/**
 * ElectronExchanger Server Code File
 * 
 * File:        socket_helper.c
 * Brief:       BRIEF
 * Author:      Chen Kangbing
 * Email:       ckblau@outlook.com
 * Modified:    2020/09/08 17:24:17
 *
 */


#include "socket_helper.h"

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

const int BUFFER_SIZE_SINGLE = 1024 * sizeof(char);


int init_socket_server(ServerSocketTypeDef *psrv) {
    int addrlen;
    int on = 1;
    int rv = -1;

    psrv->socket_srvfd = socket(AF_INET, SOCK_STREAM, 0);
    if (psrv->socket_srvfd < 0) {
        printf("Failed to create socket: %s\n", strerror(errno));
        return -1;
    }
    printf("Socket[%d] created successfully.\n", psrv->socket_srvfd);

    setsockopt(psrv->socket_srvfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));

    addrlen = sizeof(psrv->socket_srvaddr);
    memset(&psrv->socket_srvaddr, 0, addrlen);
    psrv->socket_srvaddr.sin_family = AF_INET;
    psrv->socket_srvaddr.sin_port = htons(psrv->socket_port);
    psrv->socket_srvaddr.sin_addr.s_addr = htonl(INADDR_ANY); /* 监听本机所有IP*/
    // inet_aton("192.168.0.16", &servaddr.sin_addr); /* 监听指定ip */

    rv = bind(psrv->socket_srvfd, (struct sockaddr *)&psrv->socket_srvaddr, addrlen);
    if (rv < 0) {
        printf("Failed to bind socket[%d] on port[%d], errno [%d]: %s\n", 
               psrv->socket_srvfd, psrv->socket_port, errno, strerror(errno));
        return -2;
    }

    listen(psrv->socket_srvfd, 13);
    printf("Now listening on port[%d] ...\n", psrv->socket_port);

    return 0;
}

TransmissionTypeDef *create_transmission() {
    TransmissionTypeDef *ptrans;
    ptrans = malloc(sizeof(TransmissionTypeDef));
    memset(ptrans, 0, sizeof(TransmissionTypeDef));
    return ptrans;
}

int loop_listen(ServerSocketTypeDef *psrv, TransmissionTypeDef *ptrans) {
    socklen_t len;
    // struct timeval timeout = {100, 0};    // 1, 0 == 1s
    // int rv;

    printf("Waiting for incoming client ...\n");

    ptrans->socket_clifd = accept(psrv->socket_srvfd, (struct sockaddr *)&ptrans->socket_cliaddr, &len);
    if (ptrans->socket_clifd < 0) {
        printf("Failed to accept incoming client, errno [%d]: %s\n", 
               errno, strerror(errno));
        return ptrans->socket_clifd;
    }
    printf("Incoming client <---[%s:%d]\n",
           inet_ntoa(ptrans->socket_cliaddr.sin_addr), 
           ntohs(ptrans->socket_cliaddr.sin_port));
        
    // rv = setsockopt(ptrans->socket_clifd, 
    //                 SOL_SOCKET, SO_SNDTIMEO, 
    //                 (const char *)&timeout, sizeof(timeout));
    // if (rv < 0) return 1;
    // rv = setsockopt(ptrans->socket_clifd, 
    //                 SOL_SOCKET, SO_RCVTIMEO, 
    //                 (const char *)&timeout, sizeof(timeout));
    // if (rv < 0) return 1;

    return 0;
}

int transmit_down(TransmissionTypeDef *ptrans) {
    int rv;
    int buf_size = BUFFER_SIZE_SINGLE;
    int current = 0;
    int size_rest;
    ptrans->down_buffer = malloc(buf_size);
    ptrans->down_data_size = -1;
    
    while (1) {
        size_rest = buf_size - current;
        if (size_rest < BUFFER_SIZE_SINGLE) {
            buf_size += BUFFER_SIZE_SINGLE;
            ptrans->down_buffer = realloc(ptrans->down_buffer, buf_size);
            size_rest = buf_size - current;
        }
        char *buf = ptrans->down_buffer + current;

        rv = recv(ptrans->socket_clifd, buf, size_rest, 0);
        if (rv < 0) {
            printf("thread [%ld]: ", ptrans->thread_tid);
            printf("Failed[%d] to receive data from client socket[%d], errno [%d]: %s.\n"
                   "Bad connection.\n",
                   rv, ptrans->socket_clifd, errno, strerror(errno));
            close(ptrans->socket_clifd);
            return -1;
        } else if (rv == 0) {
            printf("thread [%ld]: ", ptrans->thread_tid);
            printf("Disconnected from client socket[%d].\n"
                   "Bad connection.\n",
                   ptrans->socket_clifd);
            close(ptrans->socket_clifd);
            return -2;
        } else if (rv > 0) {
            current += rv;
            if (/*current > 10 && */ptrans->down_data_size < 0) {
                sscanf(ptrans->down_buffer, "%d:%n", &ptrans->down_data_size, &ptrans->down_header_size);
                if (ptrans->down_data_size < 0) {
                    printf("thread [%ld]: ", ptrans->thread_tid);
                    printf("Length header not received from client socket[%d].\n"
                           "Bad connection.\n",
                           ptrans->socket_clifd);
                    close(ptrans->socket_clifd);
                    return -3;
                }
            }
            if (current >= ptrans->down_data_size + ptrans->down_header_size) {
                ptrans->down_buffer[current] = 0;
                break;
            }
        }
    }
    
    if (ptrans->down_data_size + ptrans->down_header_size != current) {
        printf("thread [%ld]: ", ptrans->thread_tid);
        printf("Received data length mismatch.\n"
               "%d bytes received (excluding header), %d bytes expected.\n",
               current - ptrans->down_header_size, ptrans->down_data_size);
        close(ptrans->socket_clifd);
        return -4;
    }

    printf("thread [%ld]: ", ptrans->thread_tid);
    printf("Down transmission finished.\n"
           "Recieved %d bytes (including header) from client socket.\n",
           current);
    ptrans->down_data = ptrans->down_buffer + ptrans->down_header_size;
    return 0;
}

int transmit_up(TransmissionTypeDef *ptrans) {
    int rv;
    int current = 0;
    int size_rest = -1;
    char *buf;
    int header_sent = 0;
    int header_size = -1;
    char header[20];

    while (1) {
        if (!header_sent) {
            header_size = sprintf(header, "%d:", ptrans->up_data_size);
            size_rest = header_size;
            buf = header;
        }
        else {
            size_rest = ptrans->up_data_size - current;
            if (size_rest <= 0) {
                break;
            }
            if (size_rest > BUFFER_SIZE_SINGLE) {
                size_rest = BUFFER_SIZE_SINGLE;
            }
            buf = ptrans->up_data + current;
        }

        rv = send(ptrans->socket_clifd, buf, size_rest, 0);
        if (rv < 0) {
            printf("thread [%ld]: ", ptrans->thread_tid);
            printf("Failed to send data to client socket[%d], errno [%d]: %s.\n"
                   "The thread will now exit.\n",
                   ptrans->socket_clifd, errno, strerror(errno));
            close(ptrans->socket_clifd);
            return -1;
        } else if (rv == 0) {
            printf("thread [%ld]: ", ptrans->thread_tid);
            printf("Disconnected from socket[%d].\n"
                   "The thread will now exit.\n",
                   ptrans->socket_clifd);
            close(ptrans->socket_clifd);
            return -2;
        } else if (rv > 0) {
            if (!header_sent) {
                header_sent = 1;
            }
            else {
                current += rv;
            }   
        }
    }

    if (current != ptrans->up_data_size) {
        printf("thread [%ld]: ", ptrans->thread_tid);
        printf("Transferred data length mismatch.\n"
               "%d bytes transferred (excluding header), %d bytes expected.\n",
               current, ptrans->up_data_size);
        close(ptrans->socket_clifd);
        return -3;
    }
    
    printf("thread [%ld]: ", ptrans->thread_tid);
    printf("Up transmission finished.\n"
           "Transferred %d bytes (including header) to client socket.\n",
           current + header_size);
    return 0;
}

void shutdown_transmission(TransmissionTypeDef *ptrans) {
    // char buf[BUFFER_SIZE_SINGLE];
    // int rv;

    printf("Shutting down transmission ...\n");
    shutdown(ptrans->socket_clifd, SHUT_WR);
    shutdown(ptrans->socket_clifd, SHUT_RD);
    // wait until client disconnect 
    // rv = recv(ptrans->socket_clifd, buf, BUFFER_SIZE_SINGLE, 0);
    // if (rv < 0) {
    //     printf("Failed[%d] to shutdown client socket[%d], errno [%d]:%s.\n"
    //            "The data sent may be corrupted.\n",
    //            rv, ptrans->socket_clifd, errno, strerror(errno));
    // } 
    close(ptrans->socket_clifd);
}

void destroy_transmission(TransmissionTypeDef *ptrans) {
    if (ptrans->down_buffer) free(ptrans->down_buffer);
    if (ptrans->up_data) free(ptrans->up_data);
    free(ptrans);
}

void deinit_socket_server(ServerSocketTypeDef *psrv) {
    close(psrv->socket_srvfd);
}

